﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PIT.Labs.ProjectRepository.Owl2DotNet.Parsers;
using System.Xml.Linq;
using System.Xml;

namespace PIT.Labs.ProjectRepository.Owl2DotNet.Xsd
{
    /// <summary>
    /// Parse an ontology from an xsd file using the flooding method
    /// </summary>
    public class XsdFloodParser : OntologyParserBase
    {
        static XNamespace xsd = "http://www.w3.org/2001/XMLSchema";
        static XName element = xsd + "element";
        static XName complexType = xsd + "complexType";
        static XName simpleType = xsd + "simpleType";
        static XName sequence = xsd + "sequence";
        static XName attribute = xsd + "attribute";
        static XName simpleContent = xsd + "simpleContent";
        static XName complexContent = xsd + "complexContent";
        static XName extension = xsd + "extension";

        private XDocument xmlDoc;

        private string NameToQualified(string name)
        {
            var parts = name.Split(':');
            if (parts.Length > 1)
            {
                XNamespace nsp = xmlDoc.Root.GetNamespaceOfPrefix(parts[0]);
                return nsp.NamespaceName + "#" + parts[1];
            }else{
                return xmlDoc.Root.Attribute("targetNamespace").Value + "#" + name;
            }
        }

        private Uri NameToUri(string name)
        {
            return new Uri(NameToQualified(name));
        }

        /// <summary>
        /// Get / Set the starting element for the flooding algorithm
        /// </summary>
        public XName StartElement { get; set; }

        /// <summary>
        /// Parse the ontology from a stream containing an xsd file
        /// </summary>
        /// <param name="instream"></param>
        /// <returns></returns>
        public override IOntology Load(System.IO.Stream instream)
        {
            if (StartElement == null)
            {
                throw new InvalidOperationException("StartElement has to be set before calling this method!");
            }

            XmlReader xmlFromStream = XmlReader.Create(instream, new XmlReaderSettings() { ProhibitDtd = false });
            xmlDoc = XDocument.Load(xmlFromStream);

            var rootNamespace = xmlDoc.Root.Attribute("targetNamespace").Value;

            var ontology = new XsdOntology(rootNamespace);

            var compTypes = new Dictionary<string, XElement>();

            XElement start = xmlDoc.Descendants(element).Where(x => GetValue(x.Attribute("name")) == StartElement.ToString()).First();

            CrawlXsd(ontology, start, null);

            return ontology;
        }

        private void CrawlXsd(XsdOntology ontology, XElement start, XsdDataProperty parent)
        {
            string typeName = GetValue(start.Attribute("type"));

            var types = start.Descendants(complexType);

            if (!String.IsNullOrEmpty(typeName) || types.Count() == 0)
            {
                string qName = NameToQualified(typeName);
                // find the type node in xml 
                types = xmlDoc.Descendants(complexType).Where(x => NameToQualified(GetValue(x.Attribute("name"))) == qName);
            }

            var type = types.First();

            // and get all sub-relations from this element
            var relations = type.Elements(sequence).Elements(element);
            var specialization = type.Elements(complexContent).Elements(extension);

            if (specialization.Count() != 0)
            {
                string specName = GetValue(specialization.First().Attribute("base"));
                if (!String.IsNullOrEmpty(specName))
                {
                    string qSpecName = NameToQualified(specName);
                    var specType = xmlDoc.Descendants(complexType).Where(x => NameToQualified(GetValue(x.Attribute("name"))) == qSpecName).First();

                    relations = relations.Union(specType.Elements(sequence).Elements(element));
                }
            }

            if (relations.Count() != 0)
            {
                CreateDataPropertiesFromElements(ontology, parent, relations);
            }
            else
            {
                if (start.Elements(simpleType).Count() == 0 && types.Elements(simpleContent).Count() == 0)
                {
                    //XXX
                    //System.Console.WriteLine("Crazy: " + parent.Label);
                }
            }
        }

        private void CreateDataPropertiesFromElements(XsdOntology ontology, XsdDataProperty parent, IEnumerable<XElement> relations)
        {
            foreach (var item in relations)
            {
                var refItem = item;
                // is the element a reference?
                string refName = GetValue(item.Attribute("ref"));
                if (!String.IsNullOrEmpty(refName))
                {
                    string qRefName = NameToQualified(refName);
                    //... go to the referenced element and continue processing it...
                    refItem = xmlDoc.Descendants(element).Where(x => NameToQualified(GetValue(x.Attribute("name"))) == qRefName).FirstOrDefault();

                    if (refItem == null) return;
                }
                // Check if property for this node already exists (double reference)
                var childQName = NameToQualified(GetValue(refItem.Attribute("name")));
                var child = ontology.GetXsdDataProperty(new Uri(childQName));
                if (child == null)
                {
                    // create if needed
                    child = new XsdDataProperty(ontology, childQName, refItem, parent);
                    CrawlXsd(ontology, refItem, child);
                }
                else
                {
                    child.parents.Add(parent);
                }
            }
        }

        private static string GetValue(XAttribute xAttribute)
        {
            return xAttribute==null ? "" : xAttribute.Value;
        }
    }
}
